<template>
  <div>
    <el-tree :data="treeData" :props="defaultProps" :loading="treeDataLoading"
             show-checkbox
             node-key="catId"
             :default-expanded-keys="expandedKeys"
             :expand-on-click-node="false"
             :draggable="true"
             :allow-drop="allowDrop"
             @node-drop="nodeDrop"
    >
       <span class="custom-tree-node" slot-scope="{ node, data }">
        <span>{{ node.label }}</span>
        <span>
          <el-button
            type="text"
            size="mini"
            v-if="node.level <= 2"
            @click="() => append(data)">
            添加
          </el-button>
            <el-button
              type="text"
              size="mini"
              @click="() => edit(data)">
            编辑
          </el-button>
          <el-button
            type="text"
            size="mini"
            v-if="node.childNodes == 0"
            @click="() => remove(node, data)">
            删除
          </el-button>
        </span>
      </span>
    </el-tree>

    <el-dialog title="新增分类" :visible.sync="dialogCategoryFormVisible">
      <el-form :model="category">
        <el-form-item label="分类名称">
          <el-input v-model="category.name" autocomplete="off"></el-input>
        </el-form-item>
        <el-form-item label="是否启用">
          <el-switch
            v-model="category.showStatus"
            active-color="#13ce66"
            inactive-color="#ff4949"
            :active-value="1"
            :inactive-value="0">
          </el-switch>
        </el-form-item>
      </el-form>

      <div slot="footer" class="dialog-footer">
        <el-button @click="dialogCategoryFormVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitCategory">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
  export default {
    name: 'category',
    // import引入的组件需要注入到对象中才能使用
    components: {},
    props: {},
    data () {
      return {
        // 分类列表
        treeDataLoading: false,
        treeData: [],
        defaultProps: {
          children: 'children',
          label: 'name'
        },
        expandedKeys: [],

        // 新增分类 或 编辑分类
        category: {catId: 0, parentCid: 0, name: '', showStatus: 1, catLevel: 0, sort: 0, icon: ''},
        dialogCategoryFormVisible: false,
        dialogCategoryFormTitle: '新增分类',
        dialogCategoryFormType: 'add'
      }
    },
    // 计算属性类似于data概念
    computed: {},
    // 监控data中的数据变化
    watch: {},
    // 方法集合
    methods: {
      // 获取树形数据列表
      getTreeData () {
        this.treeDataLoading = true
        this.$http({
          url: this.$http.adornUrl('/product/category/list/tree'),
          method: 'get'
        }).then(({data}) => {
          this.treeData = data.data
          this.treeDataLoading = false
        })
      },
      // 添加分类
      append (data) {
        // 显示新增弹窗
        this.category.parentCid = data.catId
        this.category.catLevel = data.catLevel * 1 + 1
        this.dialogCategoryFormVisible = true
        this.dialogCategoryFormTitle = '新增分类'
        this.dialogCategoryFormType = 'add'
        this.category = {catId: 0, parentCid: 0, name: '', showStatus: 1, catLevel: 0, sort: 0, icon: ''}
        console.log(data)
      },
      submitCategory (data) {
        console.log(data)
        let expandedKeys = [this.category.parentCid]  // 保存展开项
        if (this.dialogCategoryFormType === 'add') {
          this.$http({
            url: this.$http.adornUrl('/product/category/save'),
            method: 'post',
            data: this.$http.adornData(this.category, false)
          }).then(({data}) => {
            if (data && data.code === 0) {
              this.$message({
                message: '添加成功',
                type: 'success'
              })
              this.dialogCategoryFormVisible = false
              this.treeData = []
              this.category = {catId: 0, parentCid: 0, name: '', showStatus: 1, catLevel: 0, sort: 0, icon: ''}
              this.getTreeData()
              this.expandedKeys = expandedKeys
            } else {
              this.$message.error(data.msg)
            }
          })
        } else {
          this.$http({
            url: this.$http.adornUrl('/product/category/update'),
            method: 'post',
            data: this.$http.adornData(this.category, false)
          }).then(({data}) => {
            if (data && data.code === 0) {
              this.$message({
                message: '修改成功',
                type: 'success'
              })
              this.dialogCategoryFormVisible = false
              this.treeData = []
              this.category = {catId: 0, parentCid: 0, name: '', showStatus: 1, catLevel: 0, sort: 0, icon: ''}
              this.getTreeData()
              this.expandedKeys = expandedKeys
            } else {
              this.$message.error(data.msg)
            }
          })

        }

      },
      // 编辑分类
      edit (data) {
        // 显示编辑弹窗
        // this.category.parentCid = data.catId
        // this.category.catLevel = data.catLevel * 1 + 1
        // 获取分类
        this.$http({
          url: this.$http.adornUrl(`/product/category/info/${data.catId}`),
          method: 'get',
          data: this.$http.adornData(null, false)
        }).then(({data}) => {
          if (data && data.code === 0) {
            this.dialogCategoryFormVisible = true
            this.dialogCategoryFormTitle = '编辑分类'
            this.dialogCategoryFormType = 'edit'
            console.log(data)
            this.category = data.category
          } else {
            this.$message.error(data.msg)
          }
        })

      },
      remove (node, data) {
        console.log(data)
        let ids = [data.catId]
        // 获取删除节点的父节点，用于删除后暂开的节点
        let expandedKeys = [node.parent.data.catId]
        // 删除
        this.$confirm(`确定删除${data.name}分类?`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$http({
            url: this.$http.adornUrl('/product/category/delete'),
            method: 'post',
            data: this.$http.adornData(ids, false)
          }).then(({data}) => {
            if (data && data.code === 0) {
              this.$message({
                message: '删除成功',
                type: 'success'
              })
              this.getTreeData()
              this.expandedKeys = expandedKeys
            } else {
              this.$message.error(data.msg)
            }
          })
        }).catch(() => {
        })
      },
      /**
       * 拖拽时判定目标节点能否被放置
       * @param draggingNode 正在拖拽的节点
       * @param dropNode 对象节点
       * @param type type 参数有三种情况：'prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
       */
      allowDrop (draggingNode, dropNode, type) {
        // 目前我们最多只允许有3层节点，于是我们计算拖到目标节点后层级不能超过3，于是我们来计算下最多层级数 <= 3
        // 算法： 拖动的节点那一坨的节点的层级数 + 目标节点的层级数 <= 3
        // 拖动的那一坨节点的层级数 = 最深的节点的层级 - 当前拖拽的节点的层级 + 1
        // ① 关键在于最深层级数的算法如何计算： 也就是在拖动的节点和他的子孙节点中遍历获取得到最深的节点的层级数
        // ② 还有一个就是目标节点的判断，通过type获取，如果是在dropNode之前那么目标节点为dropNode的父节点；如果在dropNode中，父节点就是dropNode；如果在dropNode之后那么目标节点为dropNode的父节点
        // 形象的比喻： 挂砝码，拖动下面2个砝码，挂到其他砝码上，每一串砝码最多3个
        // 获取最深的节点层级数,遍历所有节点使用冒泡排序法
        // console.log(draggingNode)
        // console.log(dropNode)
        // console.log(type)
        let destNode = null // 目标节点
        // 1、 获取目标节点
        if (type === 'prev' || type === 'next') {
          destNode = dropNode.parent // 拖拽到对象节点的前面或后面 则目标节点为该对象的父节点
        } else {
          destNode = dropNode // 推拽到对象节点内部，则目标节点为该对象节点即可
        }
        // 2、获取最深层级
        let deepLevel = {level: draggingNode.level} // 想要按引用传递，必须创建对象来存储值，不然会是按照值传递的
        for (let i = 0; i < draggingNode.childNodes.length; i++) {
          // 遍历获取节点，递归获取
          this.findDeepLevel(draggingNode.childNodes[i], deepLevel)
        }
        // console.log('deepLevel', deepLevel.level)
        // console.log('draggingNode.level', draggingNode.level)
        // console.log('destNode.level', destNode.level, destNode.data.name)
        // 3、判断 (最深的节点的层级 - 当前拖拽的节点的层级 + 1 ) 是否 <= 3  ?
        if ((deepLevel.level - draggingNode.level + 1) + destNode.level <= 3) {
          return true
        } else {
          return false
        }
      },
      /**
       * 递归调用获取节点的最深层级
       * @param node
       * @param deepLevel
       */
      findDeepLevel (node, deepLevel) {
        // console.log(deepLevel.level, node.level)
        if (deepLevel.level < node.level) {
          deepLevel.level = node.level
        }
        for (let i = 0; i < node.childNodes.length; i++) {
          // 遍历获取节点，递归获取
          this.findDeepLevel(node.childNodes[i], deepLevel)
        }
      },
      // 被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置（before、after、inner）
      nodeDrop (draggingNode, dropNode, type, event) {
        console.log(draggingNode)
        console.log(dropNode)
        console.log(type)
        console.log(event)
        // 1、获取要受影响的父节点：拖拽到dropNode前面或者后面时，父节点为对象节点的父节点；否则拖拽到dropNode里面时父节点就为该对象节点dropNode
        // 2、获取父节点的所有子节点，并更新所有子节点的sort，注意：拖拽的节点要更新sort还要更新level，以及拖拽的子节点更新level
        let parentNode = null
        if (type === 'before' || type === 'after') {
          parentNode = dropNode.parent
        } else {
          parentNode = dropNode
        }
        // 遍历父节点的所有子节点，获取要修改的节点
        let updatingNode = {willUpdateNodes: []}
        let sort = 0
        for (let i = 0; i < parentNode.childNodes.length; i++) {
          let child = parentNode.childNodes[i]
          console.log(child)
          if (child.data.catId !== draggingNode.data.catId) { // 非当前拖拽节点 只需要更新排序
            let updatingChild = {
              catId: child.data.catId,
              sort: sort
            }
            updatingNode.willUpdateNodes.push(updatingChild)
          } else { // 更新当前拖拽节点的 排序和层级以及父id
            let updatingChild = {
              catId: child.data.catId,
              sort: sort,
              catLevel: child.level,
              parentCid: parentNode.data.catId || 0
            }
            updatingNode.willUpdateNodes.push(updatingChild)
            // 递归遍历子节点 更新层级
            for (let j = 0; j < child.childNodes.length; j++) {
              this.foreachChildNodesUpating(child.childNodes[j], updatingNode)
            }
          }
          sort++
        }
        console.log(updatingNode)
        // 提交后端更新
        this.$http({
          url: this.$http.adornUrl('/product/category/batchUpdate'),
          method: 'post',
          data: this.$http.adornData(updatingNode.willUpdateNodes, false)
        }).then(({data}) => {
          if (data && data.code === 0) {
            this.$message({
              message: '更新成功',
              type: 'success'
            })
            this.getTreeData() // 刷新分类
            this.expandedKeys = [parentNode.data.catId] // 展开父节点
          } else {
            this.$message.error(data.msg)
          }
        })
      },
      foreachChildNodesUpating (node, updatingNode) {
        let updatingChild = {
          catId: node.data.catId,
          catLevel: node.level
        }
        updatingNode.willUpdateNodes.push(updatingChild)
        // 递归遍历子节点 更新层级
        for (let j = 0; j < node.childNodes.length; j++) {
          this.foreachChildNodesUpating(node.childNodes[j], updatingNode)
        }
      }
    },
    // 创建完成（可以访问当前this实例）
    created () {
      this.getTreeData()
    },
    // 挂载完成（可以访问DOM元素）
    mounted () {
    },
    beforeCreate () {
    }, // 创建之前
    beforeMount () {
    }, // 挂载之前
    beforeUpdate () {
    }, // 更新之前
    updated () {
    }, // 更新之后
    beforeDestroy () {
    }, // 销毁之前
    destroyed () {
    }, // 销毁完成
    activated () {
    } // 如果页面有keep-alive缓存功能，这个函数会触发
  }
</script>

<style scoped>
  .custom-tree-node {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    font-size: 14px;
    padding-right: 8px;
  }
</style>
